package com.zhouz.admin.app.config.exception;

import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhouz.admin.app.config.redisson.RedissonService;
import com.zhouz.admin.app.db.domain.OptLog;
import com.zhouz.admin.app.db.domain.OptRequestContext;
import com.zhouz.admin.app.db.exception.*;
import com.zhouz.admin.app.db.service.OptLogService;
import com.zhouz.admin.app.db.service.OptRequestContextService;
import com.zhouz.admin.app.db.vo.LoginUserVo;
import com.zhouz.admin.app.db.vo.ResCode;
import com.zhouz.admin.app.db.vo.Result;
import com.zhouz.admin.app.jwt.JwtHelper;
import com.zhouz.admin.app.jwt.UserTokenService;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


/**
 * 异常捕获拦截
 * @author zhangpeng
 * @since 2018/8/17
 */
@ControllerAdvice
@Log4j2
@Order( value = Ordered.LOWEST_PRECEDENCE )
public class ExceptionControllerAdvice {
    @Autowired
    private OptLogService logService;
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private OptRequestContextService requestContextService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UserTokenService userTokenService;
    @Autowired
    private RedissonService redissonService;
    /**
     * 全局异常捕捉处理
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Result errorHandler(Exception ex) {
        //参数验证异常 MethodArgumentNotValidException
        if(ex instanceof ConstraintViolationException){
            return  validationExceptionHandle(ex);
        }
        if(ex instanceof MethodArgumentNotValidException){
            return  validationExceptionHandle(ex);
        }
        if(ex instanceof HttpRequestMethodNotSupportedException){
            return Result.badArgument().msg("不支持该方法提交");
        }
        log.error("=========打印日志开始============");
        //url
        log.debug("url={}", request.getRequestURL());
        //method
        log.debug("method={}", request.getMethod());
        log.error(ex.getMessage());
        ex.printStackTrace();
        log.error("=========打印日志结束============");
        return Result.fail();
    }
    @ResponseBody
    @ExceptionHandler(value = MyRunTimeException.class)
    public Result errorMyRunTimeExceptionHandler(MyRunTimeException ex) {
        log.error("=========打印日志开始MyRunTimeException============");
        log.warn("url={}", request.getRequestURL());
        log.warn("method={}", request.getMethod());
        this.save2log(ex.getArgs(), ex.getLogs(),  Result.serious(), ex,ex.getUserVo());
        return Result.serious();
    }
    @ResponseBody
    @ExceptionHandler(value = MyValidatedException.class)
    public Result errorMyRunTimeExceptionHandler(MyValidatedException ex) {
        log.warn("url={}", request.getRequestURL());
        //method
        log.warn("method={}", request.getMethod());
        return Result.validateBad().msg(ex.getMessage());
    }


    @ResponseBody
    @ExceptionHandler(value = NoLoginException.class)
    public Result errorMyRunTimeExceptionHandler(NoLoginException ex) {
        return ResCode.UN_LOGIN.result();
    }

    /**
     * token 过期
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = TokenExpiredException.class)
    public Result tokenExpiredExceptionHandler(TokenExpiredException ex) {
        log.warn("url={}", request.getRequestURL());
        //method
        log.warn("method={}", request.getMethod());
        // 如果为token过期 ，判断是否在 1t到2t内
            String token = request.getHeader(JwtHelper.LOGIN_TOKEN_KEY);
            // 分布式锁
            RLock lock = redissonService.getRLock(token);
            try {
                // 最多等待 5秒 6秒后自动释放
                boolean bs = lock.tryLock(3, 4, TimeUnit.SECONDS);
                // 获取到锁
                if (bs) {
                    log.warn("token已经过期，确定是否在 1T~ 2T之间");
                    Object refreshJwt = redisTemplate.opsForValue().get(JwtHelper.REDIST_JWT +token);
                    String userCode = refreshJwt == null ? "": refreshJwt.toString();
                    if (StringUtils.isNotBlank(userCode)){
                        log.info("token还有救，生成新的");
                        String newToken ;
                        // 重复的创建token
                        if(userCode.indexOf(JwtHelper.EXPIRE_TOKEN_PRE) > -1){
                            log.warn("重复刷新token");
                            newToken = userCode.substring(JwtHelper.EXPIRE_TOKEN_PRE.length());
                        }else{
                            // 刷新token ,创建新的token返回
                            newToken = userTokenService.generateToken(userCode);
                            // 防重复创建token  添加一个标识 相当于 清除了原来的
                            redisTemplate.opsForValue().set(JwtHelper.REDIST_JWT+token,JwtHelper.EXPIRE_TOKEN_PRE + newToken,1, TimeUnit.HOURS);
                        }
                        log.info("新的token为:"+newToken);
                        return ResCode.REFRESH_TOKEN.result(newToken);
                        // Result.reFreshtoken().data(newToken);
                    }
                } else {
                    log.warn("token已经没有救，需要重新登录");
                    return ResCode.TOKEN_EXPIRE_ERROR.result();
                }
            } catch (Exception e) {
                log.error("刷新token报错了~~~~~~~~~~~~~~~~~~~~~");
                log.error("", e);
            }finally {
                lock.unlock();
            }
        return ResCode.TOKEN_EXPIRE_ERROR.result();
    }
    /**
     * token验证失效
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = TokenNoVildException.class)
    public Result errorMyRunTimeExceptionHandler(TokenNoVildException ex) {
        return Result.tokenValidError().data(ex.getMessage());
    }

    /**
     * 参数验证失败
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = IllegalArgumentException.class)
    public Result illegalArgumentExceptionHandler(IllegalArgumentException ex) {
        log.warn("url={}", request.getRequestURL());
        //method
        log.warn("method={}", request.getMethod());
        log.error("参数错误",ex);
        return Result.badArgument().data(ex.getMessage());
    }
    /**
     * 文件过大
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = MaxUploadSizeExceededException.class)
    public Result maxUploadSizeExceededExceptionHandler(MaxUploadSizeExceededException ex) {
        log.warn("url={}", request.getRequestURL());
        //method
        log.warn("method={}", request.getMethod());
        log.warn("异常:{}",ex.getMessage());
        return Result.badArgument().msg("文件大小不能超过100M");
    }

    /**
     * 参数验证失败
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = ReSubmitException.class)
    public Result illegalArgumentExceptionHandler(ReSubmitException ex) {
        return Result.badbusy().data(ex.getMessage());
    }


    private void save2log(Object[] args, OptLog logs, Object res, Exception e, LoginUserVo loginInfoDto) {
        {
        String url = request.getRequestURL().toString();
        String ip = request.getRemoteAddr();
        new Thread(() -> {

            logs.setUrl(url);
            logs.setRequestNo(UUID.randomUUID().toString().replaceAll("-", ""));
            logs.setIp(ip);
            if(loginInfoDto != null){
                logs.setUserName(loginInfoDto.getUserName());
                logs.setUserCode(loginInfoDto.getUserId());
            }
            logService.save(logs);
            //添加详情
            OptRequestContext context = new OptRequestContext();
            ObjectMapper mapper = new ObjectMapper();
            try {
                String param = mapper.writeValueAsString(args);
                String result = mapper.writeValueAsString(res);
                context.setInParamter(param);
                context.setOutParamter(result);
                context.setRequestNo(logs.getRequestNo());
                if (e != null) {
                    context.setException(e.getMessage());
                }
                requestContextService.save(context);
            } catch (JsonProcessingException ex) {
                ex.printStackTrace();
            }
        }).start();
    }
    }
    private Result validationExceptionHandle(Exception exception) {
        String mess = "";
        exception.printStackTrace();
        if(exception instanceof ConstraintViolationException ){
            ConstraintViolationException exs = (ConstraintViolationException) exception;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            log.error("=========验证不通过哦~============");
            int i = 1;
            for (ConstraintViolation<?> item : violations) {
                //错误信息打印
                log.error(item.getMessage());
                mess += "("+i+")."+item.getMessage()+";".replace(" ","")+"\t";
                i++;
            }
            return Result.badArgument().data(mess);
        }
        if(exception instanceof MethodArgumentNotValidException){
            MethodArgumentNotValidException exs = (MethodArgumentNotValidException) exception;
            BindingResult bindingResult = exs.getBindingResult();
            log.error("=========验证不通过哦~============");
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                mess += fieldError.getDefaultMessage() + ", ";
            }
            return Result.badArgument().data(mess);
        }

        return Result.badArgument() ;
    }
//    @ExceptionHandler(value = ValidationException.class)
//    @ResponseBody
//    @ResponseStatus(HttpStatus.BAD_REQUEST)
//    public Result handle(ValidationException exception) {
//      return  validationExceptionHandle(exception);
//    }


}